# Copyright Bryan Lawrence and James Tauber, 2004
# Covered by the GPL since based on GPL code.
#
# eventually make all this xml compliant ... but not now ...
# V0.1 (Dec 17, 2004) - for Leonardo 0.4rc1 (v67)
# V0.2 (Jan 06, 2005) - more exact backwards compatiability with JT code
#                     - simple greek inserts
#                     - for Leonardo v111 up
# V0.3 (Jan 10, 2005) - inline math mode ...
# V0.4 (Jan 11, 2005) - stricter XHTML
# V0.41 (Jan 12, 2005) - verbatim/preformat handled using [pre: pre]
# V0.42 (Jan 17, 2005) - working source highlighter
# V0.50 (Jan 18, 2005) - Modify to James' new insert syntax
# V0.51 (Jan 24, 2005) - tiny change to support for _ in URL's ..
# V0.52 (Jan 28, 2005) - support doi redirects
# V0.53 (Feb 07, 2005) - Minor bug fix for preformat and embed in column 1
#                        Also sort out break on table close ...
# V0.54 (Feb 14, 2005) - Minor bug in [:... ...] syntax
#                        Actually, this was less a bug, more a difference
#                        of opinion about how this should work. An ongoing
#                        issue. Currently this formatter does not support
#                        "local" links.
# V0.55 (Feb 19, 2005) - Support rooted links, ie [:/blah xx] (but assume
#                        this is still the same as [:blah xx] for now.
#                      - Fix bug in [asdf] syntax, so that a space cannot
#                        occur within a hyperlink. (Broke some inline maths)
# V0.56 (Feb 22, 2005) - Pass embed scratch dir
# V0.57 (Feb 25, 2005) - More modifications for strict XHTML1.1
#                        <p> within blockquote, <img .../>,
# (nb can't use this at xhthml1.1): <center><table> -> <table align=center>
# V0.59 Feb 26, ugly hack for images xhtml ...means that for now, images
# can only occur in paragraph mode ... this doesn't seem to matter.
# V0.591, Mar 08, support for https links
# V0.592, Mar 24, somehow had missed delta!!
# V0.593, Mar 25, escaping [ and *
# V0.60, Mar 26, Adding support for footnotes
# V0.61, Mar 26, Also allow math mode to start with [m: for consistency
# V0.611 Apr 10, Reorder image/url to allow import of external images and
#                add a local/external check. Allow [doi: blah or doi:blah]
# V0.62  May 16, Added support for local images linked externally with:
#                [linkimage: localimage externallink]
# V0.63  May 23  Change default embedscratchdir to be more mulitplatform.
# V0.64  Jun 01  Bug fix (short para list) for close para error on list
# V0.641 Jun 22 (Ensure no print statements)
# V0.65  Sep 02  Starting work for insert css modifications
# V0.66 Dec 13 Handle valid html bug for images inserted in lists
#                       First, try handling by making all list contents paragraphs, so tables will close the
#                       paragraph properly.
# V0.67 Mar 07, 2006, Bug fix for latex images inline in lists.

import re, tempfile, os.path
from MultiSplit import MultiSplit


class WikiFormatter:
    ''' Provides wiki page formatting to produce XHTML output '''
  
    def __init__(self, link_prefix="/"):
        ''' On instantiation provide any external link prefixes, something
        to get the contents of external page inserts, and an external
        embedhandler if required (it shouldnt be) '''

        self.link_prefix = link_prefix

        #wrap images up as tables to help the css 
        self.imagewrapper= ['</p><table><tr><td>','</td></tr></table><p>']

    def __sub_link(self, matchobj):
        
        wiki_word = matchobj.group(1)
        link = self.link_prefix + re.sub(' ', '_', wiki_word).lower()
        return """<a href="%s">%s</a>""" % (link, wiki_word)

    def __remlinkimage(self,matchobj):
        return self.__linkimage(matchobj,0)
        
    def __linkimage(self,matchobj,local=1):
        ''' Returns xhtml for a linked image, locally or externally '''
        m1,m2=matchobj.group(1),matchobj.group(2)
        prefix={0:'',1:self.link_prefix}[local]
        c={0:'class="external"',1:''}[local]
        wrapper=[self.imagewrapper[0]+'<a'+c+' href="'+prefix+m2+'">',
                 '</a>'+self.imagewrapper[1]]
        s=wrapper[0]+'<img src="'+m1+'" alt="Image:'+m1+'">'+wrapper[1]
        return s
    
    def __insert(self, matchobj):
        ''' need to handle inserting resources via this to ensure
        that we flag to the formatter that this must be a new piece
        of block level html and to allow user driven css directives'''
        # self.resource_getter set in formatter
        # concept is that user does
        # [insert:url a b c]
        # to get <a b=c> url content inserted </stuff>
        matchString=matchobj.group(1)
        words=matchString.split(':')
        wiki_word = words[0]
        stuff=self.resource_getter(wiki_word).get_html()
        if len(words)==2:
            stuff=''.join(['<',words[1],'>',stuff,'</',words[1],'>'])
        elif len(words)==4:
            stuff=''.join(['<',words[1],' ',words[2],'="',words[3],'">',
                          stuff,'</',words[1],'>'])
        return '<skip>'+stuff+'</skip>'
    
    def __embedder(self,matchobj):
        ''' takes the embedded text and sends off to the embedhandler and
        replaces with simple html expressions '''
        #self.leofile set in formatter

	arg,payload=matchobj.group(1),matchobj.group(2)

        s=''.join(['<embed><script>',arg,
                   '</script><payload>',payload,'</payload></embed>'])
	s=self.embedhandler.embed(s,self.leofile)
	if arg[0:4] != 'late':
	        s='<skip>'+s+'</skip>'
	else:
		s=self.imagewrapper[0]+s+self.imagewrapper[1]
	return s

    def __footnoter(self,matchobj):
        ''' takes some maaterial and adds it to the footnote list and
        returns appropriate html for a marker '''

        fnumber=len(self.footnotelist)+1
        sfnum=str(fnumber)
        address1='fn'+sfnum
        address2=address1+'call'
        html=''.join(['<a id="',address2,'">',
                      '</a><sup><a href="#',address1,'">',sfnum,'</a></sup>'])
        text=''.join(['<div class="footnoteitem">','<a id="',address1,
                      '">',sfnum,'</a>: ',matchobj.group(1),
                      '&nbsp;<a href="#',address2,'">(ret)</a>.</div>\n'])
        
        self.footnotelist.append(text)
        return html

    def extract_title(self, page_content):
        m = re.match(r'(?m)^==([^=]+)==', page_content)
        if m:
            return m.group(1)
        else:
            return None

    def escapewikimarkup(self,s):
        ## bnl this is where I put the escaped wiki markup characters
        s = re.sub(r'\\\[','<wikiLB>',s)
        s = re.sub(r'\\\]','<wikiRB>',s)
        s = re.sub(r'\\\*','<wikiAst>',s)
        ## and these are undone after formatting is complete
        return s

    def escapereturn(self,s):
        s=re.sub(r'<wikiLB>',r'[',s)
        s=re.sub(r'<wikiRB>',r']',s)
        s=re.sub(r'<wikiAst>','*',s)
        return s

    def htmlspecial(self,s):
        ''' replace html special characters '''
        # Why don't I do this in stateless format? In case we need these
        # characters to go into a latex preparser ...
        s = re.sub(r'&', '&amp;', s)
        s = re.sub(r'<', '&lt;', s)
        s = re.sub(r'>', '&gt;', s)
        return s

    def htmlgreek(self,matchobj):
        ''' takes a string and inserts into it based on "latex-like" escaped
        greek (e.g \alpha) the xhtml code for the required symbol '''

        s=matchobj.group(1)
        
        # handle super/sub scripts
        sup,sub=r'\^(\S+)',r'_(\S+)'
        s=re.sub(sup,'<sup>\\1</sup>',s)
        s=re.sub(sub,'<sub>\\1</sub>',s)

        # can't imagine doing it more inefficiently than this ...
    
        lexicon = ('alpha','beta','gamma','delta','epsilon','zeta',
                   'eta','theta','iota','kappa','lambda','mu',
                   'nu','xi','omicron','pi','rho','sigma','tau',
                   'upsilon','phi','chi','psi','omega',
                   'Alpha','Beta','Gamma','Delta','Epsilon','Zeta',
                   'Eta','Theta','Iota','Kappa','Lambda','Mu',
                   'Nu','Xi','Omicron','Pi','Rho','Sigma','Tau',
                   'Upsilon','Phi','Chi','Psi','Omega')
    
        for item in lexicon:
            target='\\'+item
            replacement='&'+item+';'
            s=s.replace(target,replacement)

        #should add a regular expression to make normal letters italics

        return s

    def statelessformat(self, s):
        
        ''' This method simply parses s and does the substitutions that
        do not have any state carried to subsequent lines '''
        
        # ==...== to h2
        s = re.sub(r'(?m)^==([^=]+)==', '<h2>\\1</h2>', s)

        # ===...=== to h3
        s = re.sub(r'(?m)^===([^=]+)===', '<h3>\\1</h3>', s)

        # ====...==== to h4
        s = re.sub(r'(?m)^====([^=]+)====', '<h4>\\1</h4>', s)             

        # --- to emdash
        s = re.sub(r'---', '&#8212;', s)

        # [..] to wiki link
        s = re.sub(r'\[([\._A-Za-z0-9]+)\]',self.__sub_link, s)

        image=r'image: ([^ \]]+)'
        remoteimage=r'image: http://([^ \]]+)'

        # [image: filename] insert an image from the static directory
        # [image: http://blah] insert a remote linked image
        
        s=re.sub(r'\['+remoteimage+r'\]',self.imagewrapper[0]+
        '<img src="%s\\1" alt="Image: \\1"/>'%'http://'+self.imagewrapper[1],s)
        s=re.sub(r'\['+image+r'\]',self.imagewrapper[0]+
                 '<img src="%s\\1" alt="Image: \\1"/>'%self.link_prefix+
                 self.imagewrapper[1],s)
        
        # [image: filename alternate] insert an image with user alternate
        # [image: http://blah altenrate] insert a remote linked image
        s=re.sub(r'\['+remoteimage+r' ([^\]]+)\]',self.imagewrapper[0]+
        '<img src="%s\\1" alt="\\2"/>'%'http://'+self.imagewrapper[1],s)
        s=re.sub(r'\['+image+r' ([^\]]+)\]',self.imagewrapper[0]+
        '<img src="%s\\1" alt="\\2"/>'%self.link_prefix+self.imagewrapper[1],s)

        imagelink=r'imagelink: ([^ \]]+)'
        imagerem=r'imagerem: ([^ \]]+)'

        # [imagelink: filename link] insert a local image with a local link
        # [imagerem: filename remlink] insert a local image with a rem link
        # Scraping someone else's image and linking seems a bit much ...
        s=re.sub(r'\['+imagelink+r' ([^\]]+)\]',self.__linkimage,s)
        s=re.sub(r'\['+imagerem+r' ([^\]]+)\]',self.__remlinkimage,s)
       
        URL = r'((?:ftp|http|https):[^ \]]+)'

        # [http:..] to external link
        s = re.sub(r'\['+URL+r'\]', '<a class="external" href="\\1">\\1</a>', s)

        # [http:... ...] to titled and entitled external link
        s = re.sub(r'\['+URL+r' "([^"]+)" ([^\]]+)\]',
                   '<a class="external" title="\\2" href="\\1">\\3</a>', s)

        # [http:... ...] to titled external link
        s = re.sub(r'\['+URL+r' ([^\]]+)\]',
                   '<a class="external" href="\\1">\\2</a>', s)

        # [:... ...] to titled local link
        s = re.sub(r'\[:/([^ \]]+) ([^\]]+)\]',
                   '<a href="%s\\1">\\2</a>' % self.link_prefix, s)
        s = re.sub(r'\[:([^ \]]+) ([^\]]+)\]',
                   '<a href="%s\\1">\\2</a>' % self.link_prefix, s)
        #s = re.sub(r'\[:([^\]]+)\]', '<a href="\\1">\\1</a>', s)
            
        # [amazon:... ...] to link to Amazon using ISBN and link title
        s = re.sub(r'\[amazon:(\d+) ([^\]]+)\]',
                   '<a href="http://www.amazon.com/exec/obidos/tg/detail/-/\\1">\\2</a>', s)


        # [doi:doistring title] pop in the dx.doi.org redirect, can't rely on
        #browsers.
        s = re.sub(r'\[doi:(\S+) ([^\]]+)\]',
                   '<a href="http://dx.doi.org/\\1">\\2</a>',s)
        # allow either [doi:blah or [doi: blah]
        s = re.sub(r'\[doi: (\S+) ([^\]]+)\]',
                   '<a href="http://dx.doi.org/\\1">\\2</a>',s)
        
        # handle simple inline mathematics (greek, super, subscripts)
        s=re.sub(r'\[m ([^\]]+)\]',self.htmlgreek,s)
        s=re.sub(r'\[m: ([^\]]+)\]',self.htmlgreek,s)

        # do bold and italic
        # actually italics don't really work properly ...
        s=re.sub(r'\*(?! )([^\*]+)\*', '<b>\\1</b>', s)
        s=re.sub(r"''([^']+)''", '<i>\\1</i>',s)

        # last, but not least, now do the footnotes ...
        s = re.sub(r'\[fn: ([^\]]+)\]',self.__footnoter,s)
        
        return s

    def statefulformat(self,para,reset=0):

        # * 0 ... to list and | tables

        # the members of the key of the following dictionary when present
        # at the beginning of a line represent a state change
        # the tuple has the state start and close html

        # current state is carried between verbatim and embedding incidents,
        # but not between paragraph incidents ...
        # variables holding state between invocations include:
        #       self.current_state
        #       self.NewPara 
        #       self.ListOpen - terminated unless we continue list
        
        tokens={'* ':('<ul>','</p></li></ul>\n'),
                '** ':('<ul>','</p></li></ul>\n'),
                '*** ':('<ul>','</p></li></ul>\n'),
                '**** ':('<ul>','</p></li></ul>\n'),
                '0 ':('<ol style="list-style-type:decimal">','</p></li></ol>\n'),
                '00 ':('<ol style="list-style-type:lower-alpha">',
                       '</p></li></ol>\n'),
                '000 ':('<ol style="list-style-type:lower-roman">',
                        '</p></li></ol>\n'),
                '0000 ':('<ol style="list-style-type:lower-greek">',
                         '</p></li></ol>\n'),
                '|':('\n<table border="1" rules="all">',
                       '</table><br/>\n'),
                ': ':('<blockquote><p>','</p></blockquote>\n')}

        current_state=self.current_state
        
        p=''

        #code for bugfix 0.64 follows
        #(note also definition of self.PageStart in self.format)
        self.ParaClose=0 
        if self.PageStart:
            if para[0:para.find(' ')+1] in tokens: para='\n'+para
            self.PageStart=0
        #code for bugfix 0.64 concludes
        
        if self.NewPara or reset:
            #we may need to close some lists or tables    
            if len(current_state)>1:
                while len(current_state)>1:
                    type=current_state.pop()
                    p+=tokens[type][1]
                current_state=[[0]] # implies no current state
            self.NewPara=0
            self.ListOpen=0
          
        if reset: return p

        states=['\n'+x for x in tokens.keys()]
                
        tokeniser=MultiSplit(para,states)
        
        for state in tokeniser:

            if state[0] not in states:

                # make sure that it's not a heading ...
                if state[1].find('<h')==-1:
                    # this is a standard paragraph
                    p+='<p>'+state[1]
                    self.ParaClose=1
                else:
                    p+=state[1]
                    self.ParaClose=0
            else:
                
                if self.ParaClose:
                    p+='</p>'
                    self.ParaClose=0
                    
                statetype=state[0][1:]
                    
                if '*' in statetype or '0' in statetype:
                    
                    #it's a list
                    if statetype != current_state[-1]:
                        #incrementing or decrementing level
                        nl=len(statetype)-len(current_state)
                        if nl>0:
                            current_state.append(statetype)
                            p+=tokens[statetype][0]
                        else:
                            #close previous ones
                            for i in range(-nl):
                                type=current_state.pop()
                                p+=tokens[type][1]
                            p+='</p></li>' # and close the last one
                    else: p+='</p></li>'
                    p+='%s%s'%('<li><p>',state[1])

                    #needed to ensure that orphans are not created within lists
                    if '*' in current_state[-1] or '0' in current_state[-1]:
                        self.ListOpen=1
                     
                elif statetype == '|':
                     
                    # Handle tables, assume never part of lists!
                    # We assume that tables continue until the end
                    # of the paragraph, and that new rows start when
                    # there is a | in column 1 ... a space in column
                    # 1 indicates a continuation from previous row.
                    # not that i've tested this properly yet

                    if current_state[-1] not in [[0],'|']:
                        p+='''
                        <i> Tables cannot be part of a paragraph or list
                        (It is too hard to ensure xhtml compliance in a wiki).
                        Please leave a blank line before tables </i>'''

                    if current_state[-1]!='|':
                        current_state.append('|')
                        p+=tokens[statetype][0]
                    
                    # No default for colspan
                    line=re.sub(r'\| ',r'|1',statetype+state[1])
                    line='<tr>'+re.sub(r'^\|(\d)',
                                r'<td align="center" colspan="\1">&nbsp; ',line)
                    line=re.sub(
                        r'\|(\d)',
                        r' &nbsp;</td><td align="center" colspan="\1">&nbsp; ',line)
                    # need two for offline and online, why?
                    # emacs shows lines ending in ^M ... which the second
                    # one catches ...
                    line=re.sub(r'\|$','&nbsp;</td></tr>\n',line)
                    line=re.sub(r'\|\s','&nbsp;</td></tr>\n',line)
                    
                    
                    p+=line
                    
                elif statetype ==': ':

                    # handle block quotes
                    p+=tokens[': '][0]+state[1]+tokens[': '][1]
                    
            self.current_state=current_state
            if self.ParaClose:
                p+='</p>'
                self.ParaClose=0
        return p


    def format(self, page_content, leofile=None, resource_getter=None ):
        
        ''' Parse page_content and produced xhtml formatted output '''
        
        self.leofile=leofile
        self.resource_getter=resource_getter
        self.footnotelist = []

        # We'll build a list of useful words for a later searching as well
        # but it is not yet implemented ... because it will have to wait
        # until we have page formatting on submission not on extraction
        
        self.searchable=[]

        # Bryan knows we can improve much of this with regular expressions,
        # but not right now ...

        s = page_content
        s = self.htmlspecial(s) 

        #
        # general structure
        #
        #  preformatted text
        #  embedded material external html
        #  parsed and formatted wiki instructions
        #
        #  concept then is three passes through the material, rather
        #  than event driven parsing.
        #       first pass gets preformatted text
        #       second finds embedded material
        #       third does any wiki formatting
        
        # First we need to identify the preformatted text, and ensure that
        # no tokenising is done on that ...

        # Note that preformatted text cannot be part of an html <p>,
        # but it can be part of lists etc, so we can't treat
        # preformatted items as new paragraphs.
        
        s = re.sub(r'[\n$]\[pre[\n\r\f\v]([\s\S]+?)[\n\r]pre\]',
                   '<pre>\\1</pre>',s)
        s = re.sub(r'</pre>(\s*)</pre>', '\\1', s)

        #
        # then get the other chunks that will be preformatted
        #

        preblocks=MultiSplit(s,['<pre>','</pre>'])
        news=''
        
        for block in preblocks:
            news+=block[0]
            if block[0]<>'<pre>':
                s=block[1]
                # Now find anything for embedding and get it done
                s=re.sub(r'\[embed\s(.+)\s([\s\S]+?)[\n]embed\]',
                         self.__embedder,s)
                # [insert:...] to insert a resource
                s = re.sub(r'\[insert:([^\]]+)\]',self.__insert, s)
                news+=s
            else:
                news+=block[1]
        #
        # now we can do the wiki formatting proper
        #

        #yes we could avoid doing this twice, but it's simple
        blocks=MultiSplit(news,['<skip>','<pre>','</pre>','</skip>'])

        self.PageStart=1 #bf0.64
        self.NewPara=1
        self.current_state=[[0]]
        
        new=''

        for block in blocks:
            
            if block[0] not in ('<pre>','<skip>'):

                ss=''
                s=block[1]

                # empty lines -> paragraph boundaries 
                s = re.sub(r'(?m)^\s*$', '<pb>', s)

                subblocks=MultiSplit(s,['<pb>'])

                for sub in subblocks:
                    
                    s=sub[1]
                    
                    if s<>'':
                        if sub[0]=='<pb>': self.NewPara=1
                        s=self.escapewikimarkup(s)
                        s=self.statelessformat(s)
                        s=self.statefulformat(s)
                        s=self.escapereturn(s)
                        
                    ss+=s

                block=[{'</pre>':'</pre>','</skip>':'','':''}[block[0]],ss]
                  
            elif block[0]=='<skip>':

                block[0]=''

            elif block[0]=='<pre>':

                pass
                
            new+=block[0]+block[1]
            
        new+=self.statefulformat('',1)  # close any environments

        # now add any footnotes
        if len(self.footnotelist)!=0:
            new+='<div class="footnotes">'
            for item in self.footnotelist:new+=self.escapereturn(item)
            new+='</div>'

        return new
